#include <stdio.h>
#include <assert.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <netinet/in.h>

#include "inet.h"


/* connect with timeout-detecting.
 * NOTICE: This function is based on connect_nonb() in "Unix Network
 *         Programming" with a few changes.
 */
int conn_timeout(int sck, const struct sockaddr* servaddr, size_t len, int sec)
{
    assert(sck);

    int error = 0;
    int flags;
    
    /* set sck to non-block mode: */
    flags = fcntl(sck, F_GETFL, 0);
    fcntl(sck, F_SETFL, flags|O_NONBLOCK);

    /* try to connect: */
    if (connect(sck, servaddr, len)) {
        if (errno!=EINPROGRESS) return -1;
    }
    else /* connect completed immediately */
        goto DONE;

    fd_set rset, wset;
    FD_ZERO(&rset);
    FD_SET(sck, &rset);
    wset = rset;
    struct timeval tval;
    tval.tv_sec = sec;
    tval.tv_usec = 0;
    int n = select(sck+1, &rset, &wset, NULL, sec?&tval:NULL);
    if (n==0) {
        error = ETIMEDOUT;
        goto DONE;
    }
    socklen_t l;
    if (FD_ISSET(sck, &rset) || FD_ISSET(sck, &wset)) {
        l = sizeof(error);
        if (getsockopt(sck, SOL_SOCKET, SO_ERROR, &error, &l)<0)
            return -1;
    }
    else
        error = errno;

DONE:
    /* restore sck status flags: */
    fcntl(sck, F_SETFL, flags);
    if (error) {
        close(sck);
        errno = error;
        return -1;
    }
    return 0;
}
